package edison.jcr.nodetype;

import java.util.Map;

import javax.jcr.RepositoryException;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.nodetype.InvalidNodeTypeDefinitionException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.NodeDefinitionTemplate;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.NodeTypeDefinition;
import javax.jcr.nodetype.NodeTypeExistsException;
import javax.jcr.nodetype.NodeTypeIterator;
import javax.jcr.nodetype.NodeTypeManager;
import javax.jcr.nodetype.NodeTypeTemplate;
import javax.jcr.nodetype.PropertyDefinitionTemplate;
import javax.persistence.Query;
import javax.persistence.TypedQuery;

import edison.jcr.SessionContext;
import edison.persistence.Dialect;
import edison.persistence.NodeTypeBean;
import edison.persistence.PropertyDefinitionBean;
import edison.persistence.QueryUtils;
import edison.utils.Configuration;

public final class NodeTypeManagerImpl implements NodeTypeManager {

	private final SessionContext context;

	public NodeTypeManagerImpl(SessionContext context) {
		this.context = context;
	}

	public NodeType getNodeType(String nodeTypeName) throws NoSuchNodeTypeException, RepositoryException {
		NodeTypeBean nodeTypeBean;

		try {
			nodeTypeBean = QueryUtils.getNodeType(context.getEntityManager(), nodeTypeName);

		} catch (Exception e) {
			throw new RepositoryException(e);
		}

		if (nodeTypeBean == null) {
			throw new NoSuchNodeTypeException();
		}

		return new NodeTypeImpl(context, nodeTypeBean);
	}

	public boolean hasNodeType(String name) throws RepositoryException {
		try {
			return QueryUtils.hasNodeType(context.getEntityManager(), name);

		} catch (Exception e) {
			throw new RepositoryException(e);
		}

	}

	public NodeTypeIterator getAllNodeTypes() throws RepositoryException {
		try {
			TypedQuery<NodeTypeBean> query = context.getEntityManager().createQuery("select nt from NodeType nt", NodeTypeBean.class);
			TypedQuery<Long> sizeQuery = context.getEntityManager().createQuery("select COUNT(nt) from NodeType nt", Long.class);
			return new NodeTypeIteratorImpl(context, query, sizeQuery);
		} catch (Exception e) {
			throw new RepositoryException(e);
		}
	}

	public NodeTypeIterator getPrimaryNodeTypes() throws RepositoryException {
		try {
			TypedQuery<NodeTypeBean> query = context.getEntityManager().createQuery("select nt from NodeType nt where isMixin=?", NodeTypeBean.class);
			query.setParameter(1, false);
			TypedQuery<Long> sizeQuery = context.getEntityManager().createQuery("select COUNT(nt) from NodeType nt where isMixin=?", Long.class);
			sizeQuery.setParameter(1, false);
			return new NodeTypeIteratorImpl(context, query, sizeQuery);
		} catch (Exception e) {
			throw new RepositoryException(e);
		}
	}

	public NodeTypeIterator getMixinNodeTypes() throws RepositoryException {
		try {
			TypedQuery<NodeTypeBean> query = context.getEntityManager().createQuery("select nt from NodeType nt where isMixin=?", NodeTypeBean.class);
			query.setParameter(1, true);
			TypedQuery<Long> sizeQuery = context.getEntityManager().createQuery("select COUNT(nt) from NodeType nt where isMixin=?", Long.class);
			sizeQuery.setParameter(1, true);
			return new NodeTypeIteratorImpl(context, query, sizeQuery);
		} catch (Exception e) {
			throw new RepositoryException(e);
		}
	}

	public NodeTypeTemplate createNodeTypeTemplate() throws UnsupportedRepositoryOperationException, RepositoryException {
		return new NodeTypeTemplateImpl(context);
	}

	public NodeTypeTemplate createNodeTypeTemplate(NodeTypeDefinition ntd) throws UnsupportedRepositoryOperationException, RepositoryException {
		return new NodeTypeTemplateImpl(context);
	}

	public NodeDefinitionTemplate createNodeDefinitionTemplate() throws UnsupportedRepositoryOperationException, RepositoryException {
		return new NodeDefinitionTemplateImpl(context);
	}

	public PropertyDefinitionTemplate createPropertyDefinitionTemplate() throws UnsupportedRepositoryOperationException, RepositoryException {
		return new PropertyDefinitionTemplateImpl(context);
	}

	public NodeType registerNodeType(NodeTypeDefinition ntd, boolean allowUpdate) throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, UnsupportedRepositoryOperationException, RepositoryException {

		validateNodeTypeDefinition(ntd);
		boolean existsNodeType = QueryUtils.hasNodeType(context.getEntityManager(), ntd.getName());
		if (!allowUpdate && existsNodeType)
			throw new NodeTypeExistsException();
		else if (allowUpdate && existsNodeType) {
			// TODO Update
		} else if (!allowUpdate && !existsNodeType) {
			NodeTypeBean nodeTypeBean = new NodeTypeBean(ntd);
			context.getEntityManager().getTransaction().begin();
			context.getEntityManager().persist(nodeTypeBean);
			dorpAndCreateNodeTable(nodeTypeBean);
			context.getEntityManager().getTransaction().commit();
		}

		NodeTypeBean nodeTypeBean = QueryUtils.getNodeType(context.getEntityManager(), ntd.getName());

		return new NodeTypeImpl(context, nodeTypeBean);

	}

	private void dorpAndCreateNodeTable(NodeTypeBean nodeTypeBean) throws RepositoryException {
		Dialect dialect = Configuration.getDialect();
		dialect.dropNodeTable(context, nodeTypeBean.getName());
		dialect.createNodeTable(context, nodeTypeBean.getName(), nodeTypeBean.columns());
	}

	private void dorpNodeTable(String name) throws RepositoryException {
		Dialect dialect = Configuration.getDialect();
		dialect.dropNodeTable(context, name);
	}

	private void validateNodeTypeDefinition(NodeTypeDefinition ntd) throws InvalidNodeTypeDefinitionException {
		// TODO validateNodeTypeDefinition

	}

	public NodeTypeIterator registerNodeTypes(NodeTypeDefinition[] ntds, boolean allowUpdate) throws InvalidNodeTypeDefinitionException, NodeTypeExistsException, UnsupportedRepositoryOperationException, RepositoryException {
		// TODO registerNodeTypes
		return null;
	}

	public void unregisterNodeType(String name) throws UnsupportedRepositoryOperationException, NoSuchNodeTypeException, RepositoryException {
		boolean success;
		try {
			context.getEntityManager().getTransaction().begin();
			success = QueryUtils.deleteNodeType(context.getEntityManager(), name);
			dorpNodeTable(name);
			context.getEntityManager().getTransaction().commit();
		} catch (Exception e) {
			context.getEntityManager().getTransaction().rollback();
			throw new RepositoryException(e);
		}

		if (!success)
			throw new NoSuchNodeTypeException();

	}

	public void unregisterNodeTypes(String[] names) throws UnsupportedRepositoryOperationException, NoSuchNodeTypeException, RepositoryException {
		// TODO unregisterNodeTypes

	}
}